Form Based Login with JAAS on JBoss and ZK
Introduction
The talk shows how to use ZK to implement form based login for JAAS on JBoss 5.x. I assume you know how to secure your web application using form base login and a plain HTML or JSP form. If not, then first have a look at JBoss Wiki[1] or other resources.
When you use ZK, you can use plain HTML (or JSP) login form with action j_security_check
and input fields j_username
and j_password
as well. However, when developing the login form, you may want as well:
- Only one login page, not the login and error page.
- Validate the user input (for non-null values) before performing the login.
- Inform the user, why login failed.
- Have the login form with the same look&feel as the rest of your ZK application.
- And maybe more control about what's going on.
I will not explain everything in detail, download the example and look into the code. The talk also continues at Ajax and ZK Based Login with JAAS on JBoss.
JAAS and DB
JAAS is flexible in a way how to authenticate the user. JBoss provides several so called login modules. Usually, you need to check the username and password against the database, so we will use org.jboss.security.auth.spi.DatabaseServerLoginModule
. The login module may be configured site-wide in the conf/login-config.xml
. But I prefer to have the configuration packed into my deployment EAR or WAR. In JBoss, starting from 5.x, it's easy. Just make any file named xxx-jboss-beans.xml
(e.g. login-jboss-beans.xml
) in the META-INF
directory of your EAR (or META-INF in the EJB module, or WEB-INF of WAR):
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="urn:jboss:bean-deployer:2.0">
<application-policy xmlns="urn:jboss:security-beans:1.0" name="zkformlogin">
<authentication>
<login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule"
flag="required">
<!-- <module-option name="hashAlgorithm">MD5</module-option> BASE64 also possible-->
<!-- <module-option name="unauthenticatedIdentity">guest</module-option> -->
<module-option name="dsJndiName">java:/DefaultDS</module-option>
<module-option name="principalsQuery">SELECT password FROM User WHERE username=?</module-option>
<module-option name="rolesQuery">SELECT role, 'Roles' FROM UserRoles, User WHERE User.username=? AND User.id = UserRoles.user_id</module-option>
</login-module>
</authentication>
</application-policy>
</deployment>
The example uses the data source bind on java:/DefaultDS
and assumes that there are tables User
and UserRoles
in the DB. In the element rolesQuery
the string 'Roles'
means the groups of roles -- the role group 'Roles'
is the same for all users in the example. You may easily extend the DB schema to contain different group roles as well (i.e. one more column it the table UserRoles
).
Note, for JBoss 4.x this mechanism does not work, but you may use dynamic login config [2] instead.
The username of the logged-in user may be accessed:
- By the HTTP servlet request.
javax.servlet.http HttpServletRequest.getRemoteUser()
returns the username and the user role may be checked byjavax.servlet.http HttpServletRequest.isUserInRole(String role)
. - By the session context in a session bean.
javax.ejb.SessionContext.getCallerPrincipal().getName()
returns the username and the user role may be checked byjavax.ejb.SessionContext.getCallerPrincipal().isCallerInRole(String role)
.
However, one usually needs more information from the DB about the logged in user, like the real name, email, etc. Also, I usually do not access the DB directly, I like to use JPA/Hibernate. So I have made two entities: User
and UserRoles
to access the information about the user in the DB, and the session bean UserDao
, which can get the User object for the currently logged-in user. The class Identity
provides the simplified way how to access the user information from UserDao
stores it in the HTTP session, see the example code.
JBoss JAAS uses ThreadLocal class, so it does not work, when it is accessed by other thread than the thread processing the request. For ZK the best solution is to disable the event thread. It is disabled by default in ZK 5. Disable it for ZK 3 by adding to your zk.xml
:
<system-config>
<disable-event-thread />
</system-config>
ZK Login Form
In addition to the standard form login, the JBoss guys has made a nice class org.jboss.web.tomcat.security.ExtendedFormAuthenticator
, which puts into the HTTP session username (under the key j_username
) and the exception (under the key j_exception
), when the login fails. Thus, one can:
- make the same page for the login and error page,
- analyse, why the login has failed,
- and prefill the username into the input field if the login has failed.
To use this ExtendedFormAuthenticator
, just make a file WEB-INF/context.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<Context cookies="true" crossContext="false">
<Valve className="org.jboss.web.tomcat.security.ExtendedFormAuthenticator"
includePassword="false" ></Valve>
</Context>
Then, the form login in web.xml
may be:
<login-config>
<auth-method>FORM</auth-method>
<realm-name>ZK Form Login Demo</realm-name>
<form-login-config>
<form-login-page>/public/login.zul</form-login-page>
<form-error-page>/public/login.zul</form-error-page>
</form-login-config>
</login-config>
To use the ZK look&feel, just allow public access to the URI update-uri
of the org.zkoss.zk.ui.http.DHtmlLayoutServlet
servlet. Usually, it is /zkau/*
URI. Hence, my web.xml
contains:
<security-constraint>
<display-name>Zkau and Public Unprotected</display-name>
<web-resource-collection>
<web-resource-name>HtmlAdaptor</web-resource-name>
<description>Exclude Zkau and Public</description>
<url-pattern>/zkau/*</url-pattern>
<url-pattern>/public/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
This security constrain has no <auth-constraint>
, hence it allow public access for the two defined URL patterns.
Now, you have to make a HTML POST form with action j_security_check
and input fields of names j_username
and j_password
. See
Work with Legacy Web Applications, Part I - Servlets and Forms
. For instance, the file public/login.zul
may look like:
<window title="ZK Form Login Demo" width="400px" position="cetner,center" border="normal">
<zscript><![CDATA[
// parse the j_exception
Throwable j_exception = (Throwable) sessionScope.get("j_exception");
String errMsg = null;
if (j_exception != null) {
if (j_exception instanceof javax.security.auth.login.FailedLoginException) {
errMsg = "Username and/or the password is not right. Please, try it again.";
} else {
errMsg = "Unknown exception when logging in: " + this.j_exception + " Please, contact the admin.";
}
}
]]></zscript>
<h:form method="post" id="j_security_check" action="j_security_check">
<grid>
<rows>
<row>
Username :
<textbox id="j_username" name="j_username" value="${sessionScope.j_username}" width="200px" />
</row>
<row>
Password :
<textbox id="j_password" name="j_password" type="password" width="200px" />
</row>
</rows>
</grid>
<div width="100%">
<h:input type="submit" value="Login" />
</div>
</h:form>
<div if="${errMsg != null}" width="100%" style="color:red">${errMsg}</div>
<zscript><![CDATA[
j_username.focus();
]]></zscript>
</window>
This code takes the advantage of the ExtendedFormAuthenticator
, so the the HTTP session objects j_username
and j_exception
are present when the login has failed. Similar login form is implemented in the file public/login_simple.zul
in the example.
Furthermore, before submitting the form, you may execute any code, like validate the form, seeWork with Legacy Web Applications, Part III - Validate Forms
. The main trick is made by the method org.zkoss.zk.ui.util.Clients.submitForm(...)
. Instead of the input type submit button, we may use ZK button and it's onClick action:
<button id="b_login" label="Login">
<attribute name="onClick">
// just to perform constraints checks
j_username.getValue();
j_password.getValue();
// submit the form
Clients.submitForm(j_security_check);
</attribute>
</button>
To logout, just invalidate the HTTP session. See the file public/logout.zul
in the example.
More Tricks
You can execute any code before sending the form by Clients.submitForm(j_security_check)
. E.g. you may store some more data in the HTTP session (like "remember me" checkbox value). You may even access DB and check, if the login is going to succeed by yourself. In this case, you do not need org.jboss.security.auth.spi.DatabaseServerLoginModule
, but you may be happy only with org.jboss.security.auth.spi.SimpleServerLoginModule
, org.jboss.security.auth.spi.AnonLoginModule
or the other ones. (Just take care, that your code and configuration has no any security hole, like SQL injection.)
The login page does not need to be specified in the web.xml
. You may do Clients.submitForm(j_security_check)
on other pages too, e.g. on a registration page.
Redirect Time-Out to the Login Page
When the session time-outs, you may like to redirect the user to the login page and show the user a message what has happened. Configure your zk.xml
:
<session-config>
<device-type>ajax</device-type>
<timeout-uri>/?tmout=1</timeout-uri>
<automatic-timeout/>
<!-- Make a Timer to send keep-alive. -->
<!-- <timer-keep-alive>true</timer-keep-alive> -->
</session-config>
Then the browser is redirected to the root page of the application, which is secured. Hence a web container show a login page with a parameter ?tmout=1
. You can check this parameter e.g. by:
final boolean timeout = "1".equals(Executions.getCurrent().getParameter("tmout"));
Caching the JAAS Credentials
When you allow the user to change credentials (username and password, usually, depends on your JAAS configuration) within the web application, then you may have see javax.ejb.EJBAccessException
, because JAAS tries to authenticate the request with the old credentials. Then, it is wise to add flushOnSessionInvalidation
into WEB-INF/jboss-web.xml
:
<jboss-web>
<security-domain flushOnSessionInvalidation="true">java:/jaas/zkformlogin</security-domain>
</jboss-web>
See CachingLoginCredentials in JBoss Wiki .
Example
The example (download bellow) uses the data source java:/DefaultDS
which should be the HSQL database. Also, the hibernate.hbm2ddl.auto
is set to create-drop
, so the database tables are dropped and created during the deployment. Beware! It may destroy your data! Do not use it, if your java:/DefaultDS
points to a DB with any precious data! The example has been tested with JBoss 5.1.0GA and ZK5.0.0 (ZK3.6.2, too). I have also removed some ZK libraries not required for this demo, so you get a few warnings during deployment.
You can login as demo:demo, or admin:admin. After the login, you can go to the page /admin.zul
. Every user is allowed to access this page, but for non-admin users it throws an error, because it uses the secured session bean method UserDao.getAllUsers()
. The page /admin/admin.zul
is exactly the same, but the access to it is restricted in web.xml
only for admin users.
You may try to switch public/login.zul
for public/login_simple.zul
in a <form-login-page>
element of web.xml
.
Tomcat creates "Cache-Control: no-cache" HTTP response header for every request of an authenticated resource. To enable browser caching, put disableProxyCaching="false"
attribute of the Valve
element in the context.xml
:
<Context cookies="true" crossContext="false">
<Valve className="org.jboss.web.tomcat.security.ExtendedFormAuthenticator"
includePassword="false" disableProxyCaching="false"></Valve>
</Context>
Download
zkformlogin-ear.ear - the older one with ZK3.6.2
Summary
You can use ZK elements in a form based JAAS login and you can execute your code before the login form is submitted. The JAAS restricts the access to your web application and session beans. However, the AJAX calls are not secured - anyone can access the /zkau/*
URI, but it should not be a problem, because the potential malicious user has no valid session.
See Also
- Next part of this talk: Ajax and ZK Based Login with JAAS on JBoss
- Work with Legacy Web Applications, Part I - Servlets and Forms
- Work with Legacy Web Applications, Part III - Validate Forms
References:
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |